#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
# - http://heavyz.blogspot.com/
# - http://buggarden.blogspot.com/
#
# Datastore model classes for the Foods N' Roses webapp.
#
# Created on 2008-08-15.
# $Id: models.py 291 2008-12-03 10:17:53Z heavyzheng $
#

import logging

from google.appengine.ext import db
from google.appengine.api import users

#---------------------------------------------------------------------------------------------------
# RestoTag
#---------------------------------------------------------------------------------------------------

class RestoTag(db.Model):
    """Entity class for a tag on restaurant.
    """
    
    name  = db.StringProperty(required=True)
    count = db.IntegerProperty(default=0)

    @staticmethod
    def update_tag_count(name, delta):
        tag = RestoTag.gql("WHERE name=:name", name=name).get()
        if tag is None and delta > 0:
            tag = RestoTag(name=name, count=delta)
            tag.put()
        elif tag is not None:
            tag.count += delta
            if tag.count <= 0:
                tag.delete()
            else:
                tag.put()


#---------------------------------------------------------------------------------------------------
# Resto
#---------------------------------------------------------------------------------------------------

class Resto(db.Model):
    """Entity class for a restaurant.
    """
    
    FULL_STAR  = "full"
    HALF_STAR  = "half"
    EMPTY_STAR = "empty"
    
    uid          = db.StringProperty(required=True)
    name         = db.StringProperty(required=True)
    aka          = db.StringProperty()
    address      = db.PostalAddressProperty()
    post_code    = db.StringProperty()
    city         = db.StringProperty()
    
    latitude     = db.FloatProperty()
    longitude    = db.FloatProperty()
    
    places       = db.IntegerProperty(default=0)
    logo_type    = db.StringProperty()
    logo_data    = db.BlobProperty()
    desc         = db.TextProperty()

    website      = db.LinkProperty()
    email        = db.EmailProperty()
    tel_1        = db.PhoneNumberProperty()
    tel_2        = db.PhoneNumberProperty()

    tags         = db.StringListProperty(default=[])
    best_dishes  = db.StringListProperty(default=[])
    comments     = db.IntegerProperty(default=0)
    
    ratings      = db.IntegerProperty(default=0)
    average      = db.FloatProperty(default=0.0)
    cuisine      = db.FloatProperty(default=0.0)
    economy      = db.FloatProperty(default=0.0)
    service      = db.FloatProperty(default=0.0)

    submitter    = db.UserProperty()
    submit_date  = db.DateProperty(auto_now_add=True)
    
    editors      = db.ListProperty(users.User, default=[])
    managers     = db.ListProperty(users.User, default=[])

    def full_name(self):
        full_name = self.name
        if self.aka is not None:
            full_name += " (%s)" % self.aka
        return full_name
    
    def tags_str(self):
        return " ".join(self.tags)
    
    def best_dishes_str(self):
        return " ".join(self.best_dishes)
    
    def __validate(self):
        if not self.managers:
            self.managers = [ self.submitter, ]
        if self.city is not None:
            self.city = self.city.lower()
        # Validate tags.
        tags = []
        if self.tags is not None:
            for tag_name in self.tags:
                tag_name = tag_name.strip().lower()
                if len(tag_name) > 0 and tag_name not in tags:
                    tags.append(tag_name)
        self.tags = tags
        # Validate best dishes.
        best_dishes = []
        if self.best_dishes is not None:
            for dish in self.best_dishes:
                dish = dish.strip().lower()
                if len(dish) > 0 and dish not in best_dishes:
                    best_dishes.append(dish)
        self.best_dishes = best_dishes
    
    def put(self):
        # Validate properties.
        self.__validate()
        # Update tag counters.
        if self.is_saved():
            original_tags = Resto.get(self.key()).tags
        else:
            original_tags = []
        for tag_name in original_tags:
            if not tag_name in self.tags:
                RestoTag.update_tag_count(tag_name, -1)
        for tag_name in self.tags:
            if not tag_name in original_tags:
                RestoTag.update_tag_count(tag_name, 1)
        # Put the entity to datastore.
        is_new = not self.is_saved()
        key = super(Resto, self).put()
        if is_new:
            logging.info( "New Resto '%s' inserted to datastore." % self.name )
        else:
            logging.info( "Resto '%s' updated in datastore." % self.name )
        return key

    def is_current_user_editor(self):
        user = users.get_current_user()
        return user in self.editors or user in self.managers
    
    def is_current_user_manager(self):
        user = users.get_current_user()
        return user in self.managers
    
    def set_geo_location(self, latitude, longitude):
        self.latitude  = latitude
        self.longitude = longitude
    
    def has_geo_location(self):
        return self.latitude is not None and self.longitude is not None
    
    def add_rating(self, cuisine, economy, service):
        if cuisine < 1 or cuisine > 5:
            raise ValueError("Invalid cuisine rating: %d" % cuisine)
        if economy < 1 or economy > 5:
            raise ValueError("Invalid economy rating: %d" % economy)
        if service < 1 or service > 5:
            raise ValueError("Invalid service rating: %d" % service)
        self.cuisine = (self.cuisine * self.ratings + cuisine) / (self.ratings + 1)
        self.economy = (self.economy * self.ratings + economy) / (self.ratings + 1)
        self.service = (self.service * self.ratings + service) / (self.ratings + 1)
        self.average = (self.cuisine + self.economy + self.service) / 3.0
        self.ratings += 1
    
    def average_int(self):
        return self.__rating_int(self.average)
    
    def cuisine_int(self):
        return self.__rating_int(self.cuisine)
    
    def economy_int(self):
        return self.__rating_int(self.economy)
    
    def service_int(self):
        return self.__rating_int(self.service)
    
    def __rating_int(self, rating):
        if rating < 1.1:
            return 1
        elif rating < 2.1:
            return 2
        elif rating < 3.1:
            return 3
        elif rating < 4.1:
            return 4
        else:
            return 5
    
    def average_stars(self):
        return self.__rating_stars(self.average)
    
    def cuisine_stars(self):
        return self.__rating_stars(self.cuisine)
    
    def economy_stars(self):
        return self.__rating_stars(self.economy)
    
    def service_stars(self):
        return self.__rating_stars(self.service)
    
    def __rating_stars(self, rating):
        stars = []
        for i in range(0, 5):
            if rating >= i + 0.6:
                stars.append(Resto.FULL_STAR)
            elif rating >= i + 0.1:
                stars.append(Resto.HALF_STAR)
            else:
                stars.append(Resto.EMPTY_STAR)
        return stars
    

#---------------------------------------------------------------------------------------------------
# RestoComment
#---------------------------------------------------------------------------------------------------

class RestoComment(db.Model):
    """Entity class for a comment on a restaurant.
    """
    
    resto     = db.ReferenceProperty(Resto, required=True)
    cuisine   = db.IntegerProperty(default=0)
    economy   = db.IntegerProperty(default=0)
    service   = db.IntegerProperty(default=0)
    title     = db.StringProperty()
    content   = db.TextProperty()
    author    = db.UserProperty(required=True)
    post_date = db.DateTimeProperty(auto_now_add=True)
    
    def average_rating(self):
        return (self.cuisine + self.economy + self.service) / 3.0
    
    def put(self):
        is_new = not self.is_saved()
        key = super(RestoComment, self).put()
        if is_new:
            logging.info( "New comment by %s inserted to datastore." % self.author.nickname() )
            # Notify the owner Resto entity.
            self.resto.comments += 1
            self.resto.put()
        else:
            logging.info( "Comment by %s updated in datastore." % self.author.nickname() )
        return key

    def delete(self):
        id = self.key().id()
        if self.is_saved() and self.resto.comments > 0:
            self.resto.comments -= 1
            self.resto.put()
        super(RestoComment, self).delete()
        logging.info( "Comment by %s deleted from datastore." % self.author.nickname() )
    
    def is_current_user_author(self):
        user = users.get_current_user()
        return user is not None and user == self.author


#---------------------------------------------------------------------------------------------------
# RestoRating
#---------------------------------------------------------------------------------------------------

class RestoRating(db.Model):
    """Entity class for a rating on a restaurant.
    """

    resto = db.ReferenceProperty(Resto, required=True)
    user = db.UserProperty()
    ip_address = db.StringProperty()
    quality_rating = db.RatingProperty()
    service_rating = db.RatingProperty()
    expense_rating = db.RatingProperty()
    favorite = db.BooleanProperty()
    rate_date = db.DateProperty(auto_now_add=True)


#---------------------------------------------------------------------------------------------------
# Transaction functions
#---------------------------------------------------------------------------------------------------

# TODO:




